package com.github.elliot.gatewaycenter.filter;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.github.elliot.gatewaycenter.common.constant.AuthConstant;
import com.github.elliot.gatewaycenter.domain.RequestHeadsValidateObj;
import com.github.elliot.gatewaycenter.filter.order.OrderEnum;
import com.github.elliot.gatewaycenter.util.AESEncryptionUtil;
import io.netty.buffer.ByteBufAllocator;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.*;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

@Slf4j
@Component
public class RequestFilter implements GlobalFilter, Ordered {

    @Value("${aes.secretKey:4gl3fb6hS47jzfpXDzuiWkeLVw724JDMv/9My9JQ618=}")
    private String aesSecretKey;

    @Value("${request.is-open-security-check:false}")
    private Boolean isOpenRequestSecurityCheck;

    @Value("${permit.url-list}")
    private List<String> permitUrlList;

    Map<String, String> redisMap = new HashMap<>();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        // 开关和白名单
        if(BooleanUtils.isNotTrue(isOpenRequestSecurityCheck) || isInWhiteList(request)){
            return chain.filter(exchange);
        }
        RequestHeadsValidateObj requestHeadsValidateObj = validateRequestHeadSecurity(exchange);
        HttpMethod method = request.getMethod();
        // GET DELETE 不过滤
        if (method == HttpMethod.GET || method == HttpMethod.DELETE) {
            String requestData = request.getPath().value();
            validateRequestSecurity(requestData, requestHeadsValidateObj);
            return chain.filter(exchange);
        }
        // 非json类型，不过滤
        if (!isJsonRequest(exchange)) {
            return chain.filter(exchange);
        }
        ServerHttpRequestDecorator httpRequestDecorator = requestDecorator(exchange, requestHeadsValidateObj);
        return chain.filter(exchange.mutate().request(httpRequestDecorator).build())
                .then(Mono.fromRunnable(() -> {
                    redisMap.remove(requestHeadsValidateObj.getNonce());
                }));
    }

    private ServerHttpRequestDecorator requestDecorator(ServerWebExchange exchange,
                                                        RequestHeadsValidateObj requestHeadsValidateObj) {
        return new ServerHttpRequestDecorator(exchange.getRequest()) {
            @Override
            public Flux<DataBuffer> getBody() {
                Flux<DataBuffer> body = super.getBody();
                return body.buffer().handle((dataBuffers, sink) -> {
                    DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
                    DataBuffer join = dataBufferFactory.join(dataBuffers);
                    byte[] content = new byte[join.readableByteCount()];
                    join.read(content);
                    DataBufferUtils.release(join);
                    String bodyStr = new String(content, StandardCharsets.UTF_8);
                    log.info("收到的数据:{}", bodyStr);
                    if(StringUtils.isNoneBlank(bodyStr)){
                        JSONObject jsonObject = JSON.parseObject(bodyStr);
                        Object payload = jsonObject.get("payload");
                        String decrypt;
                        if(Objects.nonNull(payload)){
                            String payloadStr = payload.toString();
                            SecretKey secretKey = AESEncryptionUtil.loadKey(aesSecretKey);
                            try {
                                decrypt = AESEncryptionUtil.decrypt(payloadStr, secretKey);
                                log.info("解密后的数据:{}", decrypt);
                                validateRequestSecurity(decrypt, requestHeadsValidateObj);
                                byte[] bytes = decrypt.getBytes();
                                NettyDataBufferFactory nettyDataBufferFactory =
                                        new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
                                DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length);
                                buffer.write(bytes);
                                sink.next(buffer);
                            } catch (Exception e) {
                                sink.error(new RuntimeException(e));
                            }
                        }
                    }
                });
            }

            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders httpHeaders = new HttpHeaders();
                httpHeaders.putAll(super.getHeaders());
                // 由于修改了请求体的body，导致content-length长度不确定，因此需要删除原先的content-length
                httpHeaders.remove(HttpHeaders.CONTENT_LENGTH);
                httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
                return httpHeaders;
            }

        };
    }

    public boolean isJsonRequest(ServerWebExchange exchange) {
        String header = exchange.getRequest().getHeaders().getFirst(HttpHeaders.CONTENT_TYPE);
        return StringUtils.startsWithIgnoreCase(header, MediaType.APPLICATION_JSON_VALUE);
    }

    @Override
    public int getOrder() {
        return OrderEnum.REQUEST_FILTER.getOrder();
    }

    private boolean isInWhiteList(ServerHttpRequest request){
        if(CollectionUtil.isEmpty(permitUrlList)){
            return false;
        }
        String path = request.getPath().value();
        return permitUrlList.stream().anyMatch(whitePath ->
                path.equals(whitePath) ||
                        path.startsWith(whitePath.replace("/**", ""))
        );
    }

    private RequestHeadsValidateObj validateRequestHeadSecurity(ServerWebExchange exchange){
        ServerHttpRequest request = exchange.getRequest();
        HttpHeaders headers = request.getHeaders();
        String token = headers.getFirst(AuthConstant.AUTHORIZATION_HEAD);
        String timestamp = headers.getFirst(AuthConstant.TIMESTAMP_HEAD);
        String nonce = headers.getFirst(AuthConstant.NONCE_HEAD);
        String sign = headers.getFirst(AuthConstant.SIGN_HEAD);

        if(StringUtils.isBlank(token)){
            throw new RuntimeException("用户未登录");
        }
        if(StringUtils.isBlank(timestamp) || StringUtils.isBlank(nonce) || StringUtils.isBlank(sign)){
            throw new RuntimeException("参数异常");
        }
        redisMap.remove(nonce);
        if(Instant.now().toEpochMilli() - Long.parseLong(timestamp) > 500 * 1000 * 60){
            // throw new RuntimeException("请求过期");
        }
        if(StringUtils.isNoneBlank(redisMap.get(nonce))){
            throw new RuntimeException("重复请求");
        }else{
            redisMap.put(nonce, nonce);
        }
        return new RequestHeadsValidateObj(token, timestamp, nonce, sign);
    }

    private void validateRequestSecurity(String body, RequestHeadsValidateObj requestHeadsValidateObj){
        // 校验签名
        checkSign(requestHeadsValidateObj, body);
    }

    private void checkSign(RequestHeadsValidateObj requestHeadsValidateObj, String requestData){
        String token = requestHeadsValidateObj.getToken();
        String timestamp = requestHeadsValidateObj.getTimestamp();
        String nonce = requestHeadsValidateObj.getNonce();
        String sign = requestHeadsValidateObj.getSign();
        String data = token + timestamp + nonce + requestData;
        String signData = DigestUtil.sha256Hex(data.getBytes(StandardCharsets.UTF_8));
        if(!StringUtils.equals(signData, sign)){
            throw new RuntimeException("请求数据被篡改");
        }
    }

}
